// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.linqingying.gitcode.authentication.ui

import com.intellij.collaboration.messages.CollaborationToolsBundle
import com.intellij.collaboration.ui.CollaborationToolsUIUtil.asObservableIn
import com.intellij.collaboration.ui.HorizontalListPanel
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.components.service
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.ui.AnimatedIcon
import com.intellij.ui.components.ActionLink
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.Panel
import com.intellij.util.ui.NamedColorUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.future.asDeferred
import com.linqingying.gitcode.api.GitCodeApiRequestExecutor
import com.linqingying.gitcode.api.GitCodeServerPath
import com.linqingying.gitcode.authentication.GCOAuthService
import com.linqingying.gitcode.authentication.accounts.GCAccountManager
import com.linqingying.gitcode.i18n.GitCodeBundle.message
import com.linqingying.gitcode.ui.util.Validator
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import java.util.concurrent.CancellationException
import javax.swing.JComponent
import javax.swing.JLabel

internal class GCOAuthCredentialsUi(
  private val cs: CoroutineScope,
  val factory: GitCodeApiRequestExecutor.Factory,
  private val isAccountUnique: UniqueLoginPredicate
) : GCCredentialsUi() {

  override fun getPreferredFocusableComponent(): JComponent? = null

  override fun getValidator(): Validator = { null }

  override suspend fun login(server: GitCodeServerPath): Pair<String, String> {
    val token = acquireToken()
    val executor = factory.create(server, token)
    val login = GCTokenCredentialsUi.acquireLogin(server, executor, isAccountUnique, null)
    return login to token
  }

  override fun handleAcquireError(error: Throwable): ValidationInfo = GCTokenCredentialsUi.handleError(error)

  override fun setBusy(busy: Boolean) = Unit

  override fun Panel.centerPanel() {
    row {
      label(message("label.login.progress")).applyToComponent {
        icon = AnimatedIcon.Default()
        foreground = NamedColorUtil.getInactiveTextColor()
      }

      addWarningForPersistentCredentials(
        cs,
        service<GCAccountManager>().canPersistCredentials,
        ::panel
      )
    }
  }

  private suspend fun acquireToken(): String {
    val credentialsFuture = GCOAuthService.instance.authorize()
    try {
      return credentialsFuture.asDeferred().await().accessToken
    }
    catch (ce: CancellationException) {
      credentialsFuture.completeExceptionally(ProcessCanceledException(ce))
      throw ce
    }
  }
}
fun addWarningForPersistentCredentials(cs: CoroutineScope,
                                       canPersistCredentials: Flow<Boolean>,
                                       panel: (Panel.() -> Unit) -> Panel,
                                       solution: ((DataContext) -> Unit)? = null) {
  panel {
    row {
      cell(HorizontalListPanel(4).apply {
        val warning =
          CollaborationToolsBundle.message(if (solution != null) "accounts.error.password-not-saved.colon" else "accounts.error.password-not-saved")
        add(JLabel(warning).apply {
          foreground = NamedColorUtil.getErrorForeground()
        })

        if (solution != null) {
          add(ActionLink(CollaborationToolsBundle.message("accounts.error.password-not-saved.link")).apply {
            addActionListener {
              if (it.source != this) return@addActionListener
              solution(DataManager.getInstance().getDataContext(this))
            }
          })
        }
      }).align(AlignX.RIGHT)
        .visibleIf(canPersistCredentials.map { !it }
          .stateIn(cs, SharingStarted.Lazily, false)
          .asObservableIn(cs))
    }
  }.align(AlignX.FILL)
}